package com.molichuxing.framework.utils;

import com.molichuxing.framework.dto.UploadFileRequestDto;
import com.molichuxing.framework.dto.UploadFileResponseDto;
import com.molichuxing.framework.thread.NamedThreadFactory;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * 上传工具类
 *
 * @Author zoumingyu
 * @Date 2020年05月22日
 */
public class UploadUtil {
    private final static Logger logger = LoggerFactory.getLogger(UploadUtil.class);

    private static final String BACKSLASH = "/";
    private static final String DOT = ".";
    // 失败重试次数
    private static final int RETRY_NUM = 2;

    private static final int WAIT_TIME = 60;
    private static final TimeUnit WAIT_TIMEUNIT = TimeUnit.SECONDS;

    private static final String CONTRACT = "contract";

    private static final String PDF_SUFFIX = ".pdf";


    /**
     * 上传线程池
     */
    private static class uploadThreadPool {

        private uploadThreadPool() {
            super();
        }

        // 线程池名称
        private final static String THREAD_NAME = "upload";
        // 线程池核心线程数
        private final static int COREPOOLSIZE = 2;
        // 最大线程数
        private final static int MAXIMUMPOOLSIZE = 10;
        // 空闲线程存活时间
        private final static int KEEPALIVETIME = 60;
        // 线程等待队列
        private static BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);

        // 线程池对象
        private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE,
                KEEPALIVETIME, TimeUnit.SECONDS, queue, new NamedThreadFactory(THREAD_NAME), new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 上传处理逻辑线程池
     */
    private static class logicThreadPool {

        private logicThreadPool() {
            super();
        }

        // 线程池名称
        private final static String THREAD_NAME = "upload_logic";
        // 线程池核心线程数
        private final static int COREPOOLSIZE = 4;
        // 最大线程数
        private final static int MAXIMUMPOOLSIZE = 12;
        // 空闲线程存活时间
        private final static int KEEPALIVETIME = 3 * 60;
        // 线程等待队列
        private static BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(30);

        // 线程池对象
        private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE,
                KEEPALIVETIME, TimeUnit.SECONDS, queue, new NamedThreadFactory(THREAD_NAME), new ThreadPoolExecutor.AbortPolicy());
    }


    /**
     * 批量上传
     *
     * @param multipartFiles   multipartFiles
     * @param uploadFileConfig 上传配置
     * @return List
     * @throws InterruptedException 异常
     */
    public static List<UploadFileResponseDto> uploadFileBatchToAll(MultipartFile[] multipartFiles,
                                                                   UploadFileRequestDto uploadFileConfig) throws InterruptedException {
        Vector<UploadFileResponseDto> result = new Vector<>();
        CountDownLatch latch = new CountDownLatch(multipartFiles.length);
        for (MultipartFile multipartFile : multipartFiles) {
            uploadThreadPool.threadPool.execute(() -> {
                result.add(uploadFileToAll(multipartFile, uploadFileConfig));
                latch.countDown();
            });
        }
        latch.await();
        return result;
    }

    /**
     * 上传
     *
     * @param multipartFile    multipartFile
     * @param uploadFileConfig 上传配置
     * @return UploadFileResponseDto
     */
    public static UploadFileResponseDto uploadFileToAll(MultipartFile multipartFile,
                                                        UploadFileRequestDto uploadFileConfig) {
        UploadFileResponseDto result;
        if (multipartFile == null) {
            return null;
        }
        // MultipartFile转换成CommonsMultipartFile
        CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) multipartFile;
        // 上传文件名
        String reqFileName = commonsMultipartFile.getOriginalFilename();
        // diskFileItem
        DiskFileItem fileItem = (DiskFileItem) commonsMultipartFile.getFileItem();

        // 判断是否存在临时文件 （判断文件是否存在内存中，不存在内存中则有临时文件）
        if (!fileItem.isInMemory()) {
            // 存在
            result = uploadByFile(uploadFileConfig, fileItem, reqFileName);
        } else {
            // 不存在
            result = uploadByMemory(uploadFileConfig, fileItem, reqFileName);
        }

        // 上传的文件名
        result.setFileName(reqFileName);
        return result;
    }

    /**
     * 根据内存中数据上传
     *
     * @param uploadFileConfig 上传配置
     * @param fileItem         fileItem
     * @param reqFileName      上传文件名
     * @return UploadFileResponseDto
     */
    private static UploadFileResponseDto uploadByMemory(UploadFileRequestDto uploadFileConfig,
                                                        DiskFileItem fileItem, String reqFileName) {
        UploadFileResponseDto result = new UploadFileResponseDto();
        // 获取上传文件后缀名
        String suffix = getSuffix(reqFileName);
        // 文件名
        String fileName = getFileName(suffix);
        // 文件夹路径
        String folderPath = getFolderPath(uploadFileConfig.getFolder());
        // 本地备份文件
        File localFile = new File(uploadFileConfig.getLocalRootPath() + BACKSLASH +
                folderPath + BACKSLASH + fileName);

        // 保存文件到本地备份
        if (!backupLocal(fileItem, localFile, fileName)) {
            logger.error("文件上传失败, 内存中文件数据写入本地备份失败");
            throw new RuntimeException("文件上传失败");
        }
        // 判断是否需要转换图片
        boolean needImage = needImage(suffix, uploadFileConfig.getFolder());
        Future<String> imgFuture = null;
        if (needImage) {
            // 需要转换图片，开启线程，将流中的数据保存在本地备份中，并转换成图片上传oss
            imgFuture = logicThreadPool.threadPool.submit(() ->
                    pdfToImageAndUpload(uploadFileConfig, localFile, folderPath, null, fileName));
        }
        // 上传文件流 oss
        result.setFileUrl(uploadOss(uploadFileConfig, localFile, folderPath, fileName));
        try {
            // 等待图片上传完成获取url
            if (needImage) {
                result.setImgUrl(imgFuture.get(WAIT_TIME, WAIT_TIMEUNIT));
            }
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.error("根据内存中数据上传获取转换图片异常：", e);
            DingTalkUtil.exceptionSend("根据内存中数据上传获取转换图片异常");
        }

        return result;
    }

    /**
     * 根据文件上传
     *
     * @param uploadFileConfig 上传配置
     * @param fileItem         fileItem
     * @param reqFileName      上传文件名
     * @return UploadFileResponseDto
     */
    private static UploadFileResponseDto uploadByFile(UploadFileRequestDto uploadFileConfig,
                                                      DiskFileItem fileItem, String reqFileName) {
        UploadFileResponseDto result = new UploadFileResponseDto();
        // 获取上传文件后缀名
        String suffix = getSuffix(reqFileName);
        // 文件名
        String fileName = getFileName(suffix);
        // 文件路径
        String folderPath = getFolderPath(uploadFileConfig.getFolder());
        // 本地备份文件
        File localFile = new File(uploadFileConfig.getLocalRootPath() + BACKSLASH +
                folderPath + BACKSLASH + fileName);
        // 临时文件地址
        File tempFile = fileItem.getStoreLocation();
        // 判断是否需要转换图片
        boolean needImage = needImage(suffix, uploadFileConfig.getFolder());
        Future<String> imgFuture = null;
        if (needImage) {
            // 需要转换图片，开启线程，将流中的数据保存在本地备份中，并转换成图片上传oss
            imgFuture = logicThreadPool.threadPool.submit(() ->
                    pdfToImageAndUpload(uploadFileConfig, tempFile, folderPath, localFile.getParent(), fileName));
        }
        // 上传文件 oss
        result.setFileUrl(uploadOss(uploadFileConfig, tempFile, folderPath, fileName));
        // 开启线程, 从oss下载到本地备份
        logicThreadPool.threadPool.execute(() ->
                backupLocal(uploadFileConfig, folderPath, result.getFileUrl(), localFile, fileName));
        try {
            // 等待图片上传完成获取url
            if (needImage) {
                result.setImgUrl(imgFuture.get(WAIT_TIME, WAIT_TIMEUNIT));
            }
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.error("根据文件上传获取转换图片异常：", e);
            DingTalkUtil.exceptionSend("根据文件上传获取转换图片异常");
        }

        return result;
    }

    /**
     * 根据byte[]上传
     *
     * @param fileBytes        上传文件 byte[]
     * @param fileName         文件名
     * @param uploadFileConfig 上传配置
     * @return UploadFileResponseDto
     */
    public static UploadFileResponseDto uploadByteToAll(byte[] fileBytes, String fileName,
                                                        UploadFileRequestDto uploadFileConfig) {
        return uploadByByte(uploadFileConfig, fileBytes, fileName);
    }

    /**
     * 根据byte[]上传
     *
     * @param uploadFileConfig 上传配置
     * @param fileBytes        上传文件 byte[]
     * @param fileName         文件名（带后缀）
     * @return UploadFileResponseDto
     */
    public static UploadFileResponseDto uploadByByte(UploadFileRequestDto uploadFileConfig,
                                                     byte[] fileBytes, String fileName) {
        if (fileBytes == null || fileBytes.length == 0) {
            return null;
        }
        UploadFileResponseDto result = new UploadFileResponseDto();
        // 文件夹路径
        String folderPath = getFolderPath(uploadFileConfig.getFolder());
        // 本地备份文件夹路径
        String localFolderPath = uploadFileConfig.getLocalRootPath() + BACKSLASH + folderPath;

        InputStream is = null;
        try {
            // 判断是否需要生成图片
            String suffix = getSuffix(fileName);
            boolean needImage = needImage(suffix, uploadFileConfig.getFolder());
            Future<String> imgFuture = null;
            if (needImage) {
                imgFuture = logicThreadPool.threadPool.submit(() ->
                        pdfToImageAndUpload(uploadFileConfig, fileBytes, folderPath, localFolderPath, fileName));
            }
            // 上传oss
            is = new ByteArrayInputStream(fileBytes);
            result.setFileName(fileName);
            result.setFileUrl(uploadOss(uploadFileConfig, is, folderPath, fileName));
            // 开启线程，将文件转存到本地备份
            logicThreadPool.threadPool.execute(() ->
                    backupLocal(fileBytes, new File(localFolderPath + BACKSLASH + fileName), fileName));

            if (needImage) {
                // 等待图片上传完成获取url
                result.setImgUrl(imgFuture.get(WAIT_TIME, WAIT_TIMEUNIT));
            }

        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.error("根据byte[]上传获取转换图片异常：", e);
            DingTalkUtil.exceptionSend("根据byte[]上传获取转换图片异常");
        } finally {
            // 关闭流
            IOUtils.closeQuietly(is);
        }
        return result;
    }

    private static boolean needImage(String suffix, String folder) {
        return (PDF_SUFFIX.equalsIgnoreCase(suffix) && !CONTRACT.equalsIgnoreCase(folder));
    }

    /**
     * pdf转图片文件上传oss
     *
     * @param uploadFileConfig 上传配置
     * @param file             pdf文件
     * @param folderPath       上传文件夹路径
     * @param destPath         本地备份路径
     * @param fileName         文件名称
     * @return String
     */
    private static String pdfToImageAndUpload(UploadFileRequestDto uploadFileConfig, File file, String folderPath,
                                              String destPath, String fileName) {
        String imgUrl = null;
        try {
            // 转换图片并存储到本地备份路径
            String imgFilePath = PdfToImageUtil.pdfToImage(file, destPath, fileName);
            // 上传
            File imgFile = new File(Objects.requireNonNull(imgFilePath));
            imgUrl = uploadOss(uploadFileConfig, imgFile, folderPath, imgFile.getName());
        } catch (Exception e) {
            String msg = "文件file(" + fileName + ")上传转换图片异常";
            logger.error(msg, e);
            DingTalkUtil.exceptionSend(msg + e.getMessage());
        }
        return imgUrl;
    }

    /**
     * pdf转图片文件上传oss
     *
     * @param uploadFileConfig 上传配置
     * @param bytes            pdf文件byte[]
     * @param folderPath       上传文件夹路径
     * @param destPath         本地备份路径
     * @param fileName         文件名称
     * @return String
     */
    private static String pdfToImageAndUpload(UploadFileRequestDto uploadFileConfig, byte[] bytes, String
            folderPath,
                                              String destPath, String fileName) {
        String imgUrl = null;
        try {
            // 转换图片并存储到本地备份路径
            String imgFilePath = PdfToImageUtil.pdfToImage(bytes, destPath, fileName);
            // 上传
            File imgFile = new File(Objects.requireNonNull(imgFilePath));
            imgUrl = uploadOss(uploadFileConfig, imgFile, folderPath, imgFile.getName());
        } catch (Exception e) {
            String msg = "文件file(" + fileName + ")上传转换图片异常";
            logger.error(msg, e);
            DingTalkUtil.exceptionSend(msg + e.getMessage());
        }
        return imgUrl;
    }

    private static String uploadOss(UploadFileRequestDto uploadFileConfig, InputStream is,
                                    String folderPath, String fileName) {
        return uploadOss(uploadFileConfig, is, folderPath, fileName, 0);
    }

    private static String uploadOss(UploadFileRequestDto uploadFileConfig, InputStream is,
                                    String folderPath, String fileName, int num) {
        String fileUrl = null;
        num++;
        try {
            fileUrl = OssUtil.uploadFile(uploadFileConfig.getOssBucket(), is, folderPath, fileName,
                    uploadFileConfig.getStorageEnv(), uploadFileConfig.getPrefixUrl());
        } catch (Exception e) {
            String msg = "文件流is(" + fileName + ")上传OSS异常（第" + num + "次）:";
            logger.error(msg, e);
            // 等待 1s
            sleep(1000);
            if (num < RETRY_NUM) {
                // 小于重试次数，重新上传
                fileUrl = uploadOss(uploadFileConfig, is, folderPath, fileName, num);
            } else {
                // 大于重试次数，发送钉钉通知
                DingTalkUtil.exceptionSend(msg + e.getMessage());
            }
        }
        return fileUrl;
    }

    private static String uploadOss(UploadFileRequestDto uploadFileConfig, File file,
                                    String folderPath, String fileName) {
        return uploadOss(uploadFileConfig, file, folderPath, fileName, 0);
    }

    private static String uploadOss(UploadFileRequestDto uploadFileConfig, File file,
                                    String folderPath, String fileName, int num) {
        num++;
        String fileUrl = null;
        try {
            fileUrl = OssUtil.uploadFile(uploadFileConfig.getOssBucket(), file, folderPath, fileName,
                    uploadFileConfig.getStorageEnv(), uploadFileConfig.getPrefixUrl());
        } catch (Exception e) {
            String msg = "文件file(" + fileName + ")上传OSS异常（第" + num + "次）:";
            logger.error(msg, e);
            // 等待 1s
            sleep(1000);
            if (num < RETRY_NUM) {
                // 小于重试次数，重新上传
                fileUrl = uploadOss(uploadFileConfig, file, folderPath, fileName, num);
            } else {
                // 大于重试次数，发送钉钉通知
                DingTalkUtil.exceptionSend(msg + e.getMessage());
                throw new RuntimeException("文件上传失败");
            }
        }
        return fileUrl;
    }

    private static boolean backupLocal(DiskFileItem fileItem, File destFile, String fileName) {
        return backupLocal(fileItem, destFile, fileName, 0);
    }

    private static boolean backupLocal(DiskFileItem fileItem, File destFile, String fileName, int num) {
        boolean result = false;
        num++;
        try {
            // 判断文件是否存在
            if (!destFile.exists()) {
                // 不存在，新增
                destFile.getParentFile().mkdirs();
                fileItem.write(destFile);
            } else {
                logger.error("文件({}})已经存在 ", fileName);
            }
            result = true;
        } catch (Exception e) {
            String msg = "文件(" + fileName + ")保存本地备份异常(第" + num + "次)";
            logger.error(msg, e);
            // 等待
            sleep(100);
            if (num < RETRY_NUM) {
                // 小于重试次数，重新上传
                result = backupLocal(fileItem, destFile, fileName, num);
            } else {
                System.out.println(msg + e.getMessage());
                System.out.println("-----------------------------------");
                System.out.println(msg + e);
                // 大于重试次数，发送钉钉通知
                DingTalkUtil.exceptionSend(msg + e.getMessage());
            }
        }

        return result;
    }

    private static boolean backupLocal(byte[] fileBytes, File destFile, String fileName) {
        return backupLocal(fileBytes, destFile, fileName, 0);
    }

    private static boolean backupLocal(byte[] fileBytes, File destFile, String fileName, int num) {
        boolean result = false;
        num++;
        FileOutputStream fout = null;
        try {
            // 判断文件是否存在
            if (!destFile.exists()) {
                // 不存在，新增
                destFile.getParentFile().mkdirs();
                fout = new FileOutputStream(destFile);
                fout.write(fileBytes);
            } else {
                logger.error("文件({}})已经存在 ", fileName);
            }
            result = true;
        } catch (Exception e) {
            IOUtils.closeQuietly(fout);
            String msg = "文件(" + fileName + ")保存本地备份异常(第" + num + "次)";
            logger.error(msg, e);
            // 等待
            sleep(100);
            if (num < RETRY_NUM) {
                // 小于重试次数，重新上传
                result = backupLocal(fileBytes, destFile, fileName, num);
            } else {
                // 大于重试次数，发送钉钉通知
                DingTalkUtil.exceptionSend(msg + e.getMessage());
            }
        } finally {
            IOUtils.closeQuietly(fout);
        }

        return result;
    }

    private static boolean backupLocal(UploadFileRequestDto uploadFileConfig, String folderPath,
                                       String fileUrl, File localFile, String fileName) {
        boolean result = false;

        try {
            if (!localFile.getParentFile().exists()) {
                localFile.getParentFile().mkdirs();
            }
            OssUtil.download(uploadFileConfig.getOssBucket(), localFile, folderPath, fileName);
            result = true;
        } catch (Exception e) {
            String msg = "文件(" + fileName + ")保存本地备份异常(oss下载：" + fileUrl + ")";
            logger.error(msg, e);
            DingTalkUtil.exceptionSend(msg + e.getMessage());
        }

        return result;
    }


    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static String getFileName(String suffix) {
        String fileMd5Name = Md5Util.md5Hex(UUIDUtil.getUUID());
        // if (CONTRACT.equals(uploadFileRequestDto.getFolder())) {
        // // 合同文件直接uuid MD5
        // fileMd5Name = Md5Util.md5Hex(UUIDUtil.getUUID());
        // } else {
        // // MD5文件生成文件名（减少重复文件上传，但是会扫描全文件耗时）
        // fileMd5Name = Md5Util.md5Hex(fileItem.get());
        // }
        return fileMd5Name + suffix;
    }

    private static String getFolderPath(String folder) {
        String[] dates = DateUtil.formatDate(LocalDate.now(), "yyyy-MM-dd").split("-");
        return folder + BACKSLASH + dates[0] + BACKSLASH + dates[1] + dates[2];
    }

    private static String getSuffix(String fileName) {
        return fileName.substring(fileName.lastIndexOf(DOT));
    }

    /**
     * 上传zip并解压
     *
     * @param multipartFile        multipartFile
     * @param uploadFileRequestDto 上传配置
     * @return List
     * @throws Exception 异常
     */
    public static List<UploadFileResponseDto> uploadZipFileToAll(MultipartFile multipartFile,
                                                                 UploadFileRequestDto uploadFileRequestDto) throws Exception {
        List<UploadFileResponseDto> result = null;
        if (multipartFile == null) {
            return result;
        }
        result = new ArrayList<>();
        // 文件路径
        String filePath = getFolderPath(uploadFileRequestDto.getFolder()) + BACKSLASH + LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        // 开始解压
        ZipFile zipFile = null;
        File f = null;
        InputStream ins = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            ins = multipartFile.getInputStream();
            f = new File(multipartFile.getOriginalFilename());
            inputStreamToFile(ins, f);
            try {
                zipFile = new ZipFile(f, Charset.forName("gbk"));
                Enumeration<?> entries = zipFile.entries();
                ZipEntry entry = (ZipEntry) entries.nextElement();
            } catch (Exception e) {
                zipFile = null;
                zipFile = new ZipFile(f, Charset.forName("ISO_8859_1"));
            }
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
                File targetFile = new File(uploadFileRequestDto.getLocalRootPath() + filePath + File.separator + entry.getName());
                // 保证这个文件的父文件夹必须要存在
                if (!targetFile.getParentFile().exists()) {
                    targetFile.getParentFile().mkdirs();
                }
                targetFile.createNewFile();
                // 将压缩文件内容写入到这个文件中
                is = zipFile.getInputStream(entry);
                fos = new FileOutputStream(targetFile);
                int len;
                byte[] buf = new byte[2048];
                while ((len = is.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                }
            }
        } catch (Exception e) {
            logger.error("[uploadVehicleFile]导入车辆信息异常", e);
        } finally {
            // 关流顺序，先打开的后关闭
            if (null != ins) {
                ins.close();
                ins = null;
            }
            if (null != fos) {
                fos.close();
                fos = null;
            }
            if (null != is) {
                is.close();
                is = null;
            }
        }
        File[] purchaseInvoiceFileName = new File(uploadFileRequestDto.getLocalRootPath() + filePath).listFiles(pathname -> {
            if (pathname.isFile()) {
                return true;
            } else {
                return false;
            }
        });
        if ((null != purchaseInvoiceFileName) && (purchaseInvoiceFileName.length > 0)) {
            for (File pathname : purchaseInvoiceFileName) {
                if ((null != pathname) && (pathname.length() != 0)) {
                    UploadFileResponseDto uploadFileResponseDto = new UploadFileResponseDto();
                    if (pathname.getName().contains(".")) {
                        String ossPath = OssUtil.uploadFile(uploadFileRequestDto.getOssBucket(), pathname, filePath, pathname.getName().replace("+", ""),
                                uploadFileRequestDto.getStorageEnv(), uploadFileRequestDto.getPrefixUrl());
                        uploadFileResponseDto.setFileUrl(ossPath);
                        // 判断是否pdf文件且不是合同文件
                        if (pathname.getName().toLowerCase().contains(".pdf")) {
                            String suffix = getSuffix(pathname.getName());
                            if (".pdf".equalsIgnoreCase(suffix) && !CONTRACT.equals(uploadFileRequestDto.getFolder())) {
                                // pdf文件需要转图片
                                String imgFileName = PdfToImageUtil.pdfToImage(pathname.getAbsolutePath());
                                // 将转换的图片上传至oss
                                String imgUrl = OssUtil.uploadFile(uploadFileRequestDto.getOssBucket(), new File(imgFileName), filePath,
                                        (Md5Util.md5Hex(UUIDUtil.getUUID()) + imgFileName.substring(imgFileName.lastIndexOf(DOT))).replace("+", ""),
                                        uploadFileRequestDto.getStorageEnv(), uploadFileRequestDto.getPrefixUrl());
                                uploadFileResponseDto.setImgUrl(imgUrl);
                            }
                        }
                    }
                    uploadFileResponseDto.setFileName(pathname.getName());
                    result.add(uploadFileResponseDto);
                }
            }
        }
        return result;
    }

    private static void inputStreamToFile(InputStream ins, File file) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(os);
        }
    }
}
